﻿Imports System.Diagnostics
Imports System.Collections.Generic
Imports System.Text
Imports System.Net.Sockets

Class TelnetClient

    Enum Verbs
        WILL = 251
        WONT = 252
        [DO] = 253
        DONT = 254
        IAC = 255
    End Enum

    Enum Options
        SGA = 3
    End Enum

    Public Event OnReceivData(ByVal inData As String)

    Public strErrorMsg As String
    Private tcpSocket As TcpClient


    Public Sub New(ByVal inHost As String, ByVal inPort As Integer)
        tcpSocket = New TcpClient(inHost, inPort)
    End Sub

    Public ReadOnly Property IsConnected() As Boolean
        Get
            Return tcpSocket.Connected
        End Get
    End Property

    Public Function Login(ByVal inUsername As String, ByVal inPassword As String, ByVal inTimeOut As Integer, Optional ByVal inIsOutputOn As Boolean = False) As Boolean
        Dim strValue As String = ""
        Dim blnRead As Boolean = False

        ' Read login
        blnRead = ReadForLogin(inTimeOut, strValue)
        If Not blnRead Then
            strErrorMsg = "Impossible to get login prompt (Socket close)"
            Return False
        End If
        If inIsOutputOn Then RaiseEvent OnReceivData(strValue)

        If Not strValue.TrimEnd().EndsWith(":") Then
            strErrorMsg = "Impossible to get login prompt"
            Return False
        End If
        If Not WriteLine(inUsername) Then
            strErrorMsg = "Impossible to write login (Socket close)"
            Return False
        End If

        ' Read password
        blnRead = ReadForLogin(inTimeOut, strValue)
        If Not blnRead Then
            strErrorMsg = "Impossible to get password prompt (Socket close)"
            Return False
        End If
        If inIsOutputOn Then RaiseEvent OnReceivData(strValue)

        If Not strValue.TrimEnd().EndsWith(":") Then
            strErrorMsg = "Impossible to get password prompt"
            Return False
        End If
        If Not WriteLine(inPassword) Then
            strErrorMsg = "Impossible to write password (Socket close)"
            Return False
        End If

        ' Read welcome
        blnRead = ReadForLogin(inTimeOut, strValue)
        If Not blnRead Then
            strErrorMsg = "After login impossible to read"
            Return False
        End If
        If inIsOutputOn Then RaiseEvent OnReceivData(strValue)

        Return True
    End Function

    Public Function EndSession() As Boolean
        Return Write("exit" & vbLf)
    End Function

    Public Function WriteLine(ByVal inCmd As String) As Boolean
        Return Write(inCmd & vbLf)
    End Function

    Public Function Write(ByVal inCmd As String) As Boolean
        If Not tcpSocket.Connected Then
            strErrorMsg = "Socket closed"
            Return False
        End If

        Dim buf As Byte() = System.Text.ASCIIEncoding.ASCII.GetBytes(inCmd.Replace(vbNullChar & "xFF", vbNullChar & "xFF" & vbNullChar & "xFF"))
        tcpSocket.GetStream().Write(buf, 0, buf.Length)

        Return True
    End Function

    Public Function WriteAndWait(ByVal inSendMsg As String, ByVal inStringWanted As String, ByVal inMaxTime As Integer, Optional ByVal inIsOutputOn As Boolean = True) As Integer
        If Not WriteLine(inSendMsg) Then
            strErrorMsg = "Impossible to send command"
            Return False
        End If
        Call Read("", inIsOutputOn)

        If Not WaitFor(inStringWanted, inMaxTime, inIsOutputOn) Then
            strErrorMsg = "Wait too long"
            Return False
        End If

        Return True
    End Function

    Private Function WaitFor(ByVal inStringWanted As String, ByVal inMaxTime As Integer, Optional ByVal inIsOutputOn As Boolean = True) As Boolean
        ' Get the starting time
        Dim lngStart As Long = DateTime.Now.AddSeconds(inMaxTime).Ticks
        Dim lngCurTime As Long = 0
        Dim strData As String = ""

        Read(strData, inIsOutputOn)
        While strData.ToLower().IndexOf(inStringWanted.ToLower()) = -1
            ' Timeout logic
            lngCurTime = DateTime.Now.Ticks
            If lngCurTime > lngStart Then
                Return False
            End If
            Application.DoEvents()
            Read(strData, inIsOutputOn)
        End While

        Return True
    End Function

    Private Function ReadForLogin(ByVal inTimeOut As Integer, ByRef outString As String) As Boolean
        If Not tcpSocket.Connected Then
            strErrorMsg = "Socket closed"
            Return False
        End If

        Dim strValue As New StringBuilder()
        Dim firstTime As Date = Now

        Do
            Call ParseTelnet(strValue)
            Call System.Threading.Thread.Sleep(1)
        Loop While Now.Subtract(firstTime).TotalMilliseconds < inTimeOut

        outString = strValue.ToString
        Return True
    End Function

    Public Function Read(Optional ByRef outString As String = "", Optional ByVal inIsOutputOn As Boolean = True) As Boolean
        Try
            If Not tcpSocket.Connected Then
                strErrorMsg = "Socket closed"
                Return False
            End If

            Dim bytes(tcpSocket.ReceiveBufferSize) As Byte

            tcpSocket.GetStream.Read(bytes, 0, CInt(tcpSocket.ReceiveBufferSize))

            Dim intNum0 As Integer = 0
            Dim i As Integer = 0
            For i = 0 To bytes.Length - 1
                If bytes(i) = 0 Then
                    intNum0 += 1
                    bytes(i) = 10
                Else
                    intNum0 = 0
                End If
                If intNum0 = 2 Then
                    Exit For
                End If
            Next

            Dim strData As String = Encoding.ASCII.GetString(bytes, 0, i - 1)

            outString = strData
            If inIsOutputOn Then RaiseEvent OnReceivData(strData)

            Return True

        Catch ex As Exception
            strErrorMsg = ex.Message
            Return False
        End Try
    End Function

    Private Sub ParseTelnet(ByRef sb As StringBuilder)
        Dim input As Integer
        Dim inputverb As Integer
        Dim inputoption As Integer

        While tcpSocket.Available > 0

            input = tcpSocket.GetStream().ReadByte()
            Select Case input
                Case -1
                    MsgBox("end of stream")
                Case CInt(Verbs.IAC)
                    ' interpret as command
                    inputverb = tcpSocket.GetStream().ReadByte()

                    If inputverb = -1 Then
                        MsgBox("end of stream 2")
                        Exit Select
                    End If

                    Select Case inputverb
                        Case CInt(Verbs.IAC)
                            'literal IAC = 255 escaped, so append char 255 to string
                            sb.Append(inputverb)

                        Case CInt(Verbs.[DO]), CInt(Verbs.DONT), CInt(Verbs.WILL), CInt(Verbs.WONT)
                            ' reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                            inputoption = tcpSocket.GetStream().ReadByte()
                            If inputoption = -1 Then
                                MsgBox("end of stream 3")
                                Exit Select
                            End If

                            tcpSocket.GetStream().WriteByte(CByte(Verbs.IAC))
                            If inputoption = CInt(Options.SGA) Then
                                tcpSocket.GetStream().WriteByte(If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WILL), CByte(Verbs.[DO])))
                            Else
                                tcpSocket.GetStream().WriteByte(If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WONT), CByte(Verbs.DONT)))
                            End If
                            tcpSocket.GetStream().WriteByte(CByte(inputoption))

                    End Select

                Case Else
                    sb.Append(ChrW(input))
            End Select

        End While

    End Sub

    Protected Overrides Sub Finalize()
        tcpSocket = Nothing
        MyBase.Finalize()
    End Sub

End Class
